package net

import (
	"bytes"
	"encoding/binary"
	"errors"
	"fmt"
	"io"
	"net"
	"smart-flow/server/config"
	"smart-flow/server/logger"
	"smart-flow/server/utils"
	"sync"
	"sync/atomic"
	"time"

	"go.uber.org/zap"
)

type ClientHandler interface {
	OnConnected(ip string, port int)
	OnDisconnected(ip string, port int)
	OnMessage(data *Package)
}

type Client struct {
	Ip            string
	Port          int
	Conn          *net.TCPConn
	Status        atomic.Bool
	AutoReconnect bool
	mu            sync.Mutex
	handler       ClientHandler
	aliveCh       chan struct{}
}

func (c *Client) Connect(ip string, port int, autoReconnect bool, handler ClientHandler) {
	c.Ip = ip
	c.Port = port
	c.AutoReconnect = autoReconnect
	c.handler = handler
	c.aliveCh = make(chan struct{}, 1)
	go func() {
		c.aliveCh <- struct{}{}
		for range c.aliveCh {
			go c.doConnect()
		}
	}()
}

func (c *Client) DisConnect() {
	c.Status.Store(false)
	if c.Conn == nil {
		return
	}
	_ = c.Conn.Close()
	if c.handler != nil {
		c.handler.OnDisconnected(c.Ip, c.Port)
	}
}

func (c *Client) doConnect() {
	defer func() {
		if r := recover(); r != nil {
			logger.Logger.Error("[网络] 系统异常", zap.Any("error", r))
			c.DisConnect()
			c.aliveCh <- struct{}{} // 异常崩溃恢复
		}
	}()
	for {
		addr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", c.Ip, c.Port))
		if err != nil {
			logger.Logger.Error("[网络] 解析地址错误", zap.Error(err))
			return
		}
		dialer := &net.Dialer{
			Timeout: 10 * time.Second, // 设置连接超时时间
		}
		conn, err := dialer.Dial("tcp", addr.String())
		if err != nil {
			logger.Logger.Error("[网络] 连接错误", zap.Error(err))
			if !c.AutoReconnect {
				return
			}
			time.Sleep(10 * time.Second)
			continue
		}
		c.Conn = conn.(*net.TCPConn)
		c.Status.Store(true)
		if c.handler != nil {
			c.handler.OnConnected(c.Ip, c.Port)
		}
		c.startHeartbeat()
		c.Receive()
		c.DisConnect()
		if !c.AutoReconnect {
			return
		}
		logger.Logger.Info("[网络] 15s后尝试重连服务器...")
		time.Sleep(15 * time.Second)
	}
}

func (c *Client) Receive() {
	for {
		// logger.Logger.Info("[网络] 接收报文开始")
		payload := utils.BufferPool.Get().(*bytes.Buffer)
		if !c.doReceive(payload, HeaderLength) { // 读取包长度
			utils.Release(payload)
			return
		}
		payloadLen := binary.LittleEndian.Uint32(payload.Next(HeaderLength))
		if !c.doReceive(payload, int(payloadLen-HeaderLength)) { // 根据包长度读取数据
			utils.Release(payload)
			return
		}
		if payload.Len() < int(payloadLen-HeaderLength) {
			logger.Logger.Error("[网络] 报文长度错误")
			utils.Release(payload)
			return
		}
		ok, data := Unpack(payload, payloadLen) // 解包
		if !ok {
			logger.Logger.Error("[网络] 解构报文错误")
			utils.Release(payload)
			return
		}
		if data == nil { // 忽略心跳包
			utils.Release(payload)
			logger.Logger.Info("[网络] 心跳", zap.String("ip", c.Ip))
			continue
		}
		c.handler.OnMessage(data)
		utils.Release(payload)
		// logger.Logger.Info("[网络] 接收报文完成")
	}
}

func (c *Client) doReceive(payload *bytes.Buffer, needLen int) bool {
	if needLen < 0 || needLen > config.ReadBufMaxLen {
		logger.Logger.Info("[网络] 报文长度异常", zap.Int("length", needLen))
		return false
	}
	var readLen int
	var err error
	for {
		buf := make([]byte, needLen)
		readLen, err = c.Conn.Read(buf)
		if err != nil {
			if errors.Is(err, io.EOF) || errors.Is(err, net.ErrClosed) {
				return false
			}
			logger.Logger.Error("[网络] 接收报文错误", zap.Error(err))
			return false
		}
		payload.Write(buf[:readLen])
		if readLen == needLen {
			return true
		}
		needLen -= readLen
	}
}

func (c *Client) Send(payload *bytes.Buffer) bool {
	c.mu.Lock()
	defer c.mu.Unlock()
	if c.Conn != nil && c.Status.Load() {
		_, err := c.Conn.Write(payload.Bytes())
		if err == nil {
			return true
		}
		logger.Logger.Error("[网络] 发送报文错误", zap.Error(err))
	}
	c.DisConnect()
	return false
}

func (c *Client) startHeartbeat() {
	go func() {
		timer := time.NewTicker(30 * time.Second)
		for range timer.C {
			if !c.heartbeat() {
				return
			}
		}
	}()
}

func (c *Client) heartbeat() bool {
	payload := utils.BufferPool.Get().(*bytes.Buffer)
	defer utils.Release(payload)
	data := KeepalivePackage{
		HeadPackage: HeadPackage{UiSize: 17, UiCmd: uint32(config.CommonKeepAliveReq), UiVersion: 100},
		UiBuffSize:  1,
		ChBuff:      []byte("1"),
	}
	var err error
	if err = binary.Write(payload, binary.LittleEndian, data.UiSize); err != nil {
		return false
	}
	if err = binary.Write(payload, binary.LittleEndian, data.UiCmd); err != nil {
		return false
	}
	if err = binary.Write(payload, binary.LittleEndian, data.UiVersion); err != nil {
		return false
	}
	if err = binary.Write(payload, binary.LittleEndian, data.UiBuffSize); err != nil {
		return false
	}
	if err = binary.Write(payload, binary.LittleEndian, data.ChBuff); err != nil {
		return false
	}
	return c.Send(payload)
}
